package com.seo4j.adb4j;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import static com.seo4j.adb4j.AdbProtocol.ADB_HEADER_LENGTH;

/**
 * This class provides an abstraction for the ADB message format.
 *
 * @author Cameron Gutman
 */
public class AdbMessage {
    /**
     * The command field of the message
     */
    public int command;
    /**
     * The arg0 field of the message
     */
    public int arg0;
    /**
     * The arg1 field of the message
     */
    public int arg1;
    /**
     * The payload length field of the message
     */
    public int payloadLength;
    /**
     * The checksum field of the message
     */
    public int checksum;
    /**
     * The magic field of the message
     */
    public int magic;
    /**
     * The payload of the message
     */
    public byte[] payload;

    private final ByteBuffer mMessageBuffer;

    private final ByteBuffer mDataBuffer;
    public  AdbMessage() {

        mMessageBuffer = ByteBuffer.allocate(24);
        mDataBuffer = ByteBuffer.allocate(AdbProtocol.CONNECT_MAXDATA);
        mMessageBuffer.order(ByteOrder.LITTLE_ENDIAN);
        mDataBuffer.order(ByteOrder.LITTLE_ENDIAN);

    }

    // sets the fields in the command header
    public void set(int command, int arg0, int arg1, byte[] data) {
        mMessageBuffer.putInt(0, command);
        mMessageBuffer.putInt(4, arg0);
        mMessageBuffer.putInt(8, arg1);
        mMessageBuffer.putInt(12, (data == null ? 0 : data.length));
        mMessageBuffer.putInt(16, (data == null ? 0 : checksum(data)));
        mMessageBuffer.putInt(20, command ^ 0xFFFFFFFF);
        if (data != null) {
            mDataBuffer.put(data, 0, data.length);
        }
    }
    public void set(int command, int arg0, int arg1) {
        set(command, arg0, arg1, (byte[])null);
    }
    public void set(int command, int arg0, int arg1, String data) {
        // add trailing zero
        data += "\0";
        set(command, arg0, arg1, data.getBytes());
    }
    // returns the command's message ID
    public int getCommand() {
        return mMessageBuffer.getInt(0);
    }

    // returns command's first argument
    public int getArg0() {
        return mMessageBuffer.getInt(4);
    }

    // returns command's second argument
    public int getArg1() {
        return mMessageBuffer.getInt(8);
    }

    // returns command's data buffer
    public ByteBuffer getData() {
        return mDataBuffer;
    }

    // returns command's data length
    public int getDataLength() {
        return mMessageBuffer.getInt(12);
    }

    // returns command's data as a string
    public String getDataString() {
        int length = getDataLength();
        if (length == 0) return null;
        // trim trailing zero
        return new String(mDataBuffer.array(), 0, length - 1);
    }

    private static String extractString(ByteBuffer buffer, int offset, int length) {
        byte[] bytes = new byte[length];
        for (int i = 0; i < length; i++) {
            bytes[i] = buffer.get(offset++);
        }
        return new String(bytes);
    }

    private static int checksum(byte[] data) {
        int result = 0;
        for (int i = 0; i < data.length; i++) {
            int x = data[i];
            // dang, no unsigned ints in java
            if (x < 0) x += 256;
            result += x;
        }
        return result;
    }
    /**
     * Read and parse an ADB message from the supplied input stream.
     * This message is NOT validated.
     *
     * @param in InputStream object to read data from
     * @return An AdbMessage object represented the message read
     * @throws IOException If the stream fails while reading
     */
    public static AdbMessage parseAdbMessage(InputStream in) throws IOException {
        AdbMessage msg = new AdbMessage();
        ByteBuffer packet = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);

            /* Read the header first */
        int dataRead = 0;
        do {
            int bytesRead = in.read(packet.array(), dataRead, 24 - dataRead);

            if (bytesRead < 0)
                throw new IOException("Stream closed");
            else
                dataRead += bytesRead;
        }
        while (dataRead < ADB_HEADER_LENGTH);

            /* Pull out header fields */
        msg.command = packet.getInt();
        msg.arg0 = packet.getInt();
        msg.arg1 = packet.getInt();
        msg.payloadLength = packet.getInt();
        msg.checksum = packet.getInt();
        msg.magic = packet.getInt();

            /* If there's a payload supplied, read that too */
        if (msg.payloadLength != 0) {
            msg.payload = new byte[msg.payloadLength];

            dataRead = 0;
            do {
                int bytesRead = in.read(msg.payload, dataRead, msg.payloadLength - dataRead);

                if (bytesRead < 0)
                    throw new IOException("Stream closed");
                else
                    dataRead += bytesRead;
            }
            while (dataRead < msg.payloadLength);
        }

        return msg;
    }
}
